package com.apobates.forum.utils;

import com.apobates.forum.utils.persistence.Pageable;
import com.google.gson.Gson;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;

/**
 * 前端分页的连接地址
 *
 * @author xiaofanku
 * @since 20200526
 */
public class FrontPageURL implements Serializable {
    private static final long serialVersionUID = 8019254639004479518L;
    //包含协议,schema,domain,path
    private final String baseURI;
    //查询字符串,key=查询字符串的名字,value=值
    private final Map<String, List<String>> params = new HashMap<>();
    /**
     * 每页显示记录的最大上限:100
     */
    public final static int MAX_PAGE_SIZE = 100;
    /**
     * 每页显示记录的最小下限:5
     */
    public final static int MIN_PAGE_SIZE = 5;
    /**
     * 每页显示记录的默认值:50
     */
    public final static int DEF_PAGE_SIZE = 50;
    private String pageSizeKeyName;
    
    /**
     * 使用路径初始化
     *
     * @param baseURI
     */
    public FrontPageURL(String baseURI) {
        this.baseURI = baseURI;
    }
    
    /**
     * 使用请求的当前地址初始化
     *
     * @param request http 请求
     * @param dropParamterName 希望过滤的查询参数名称
     */
    public FrontPageURL(HttpServletRequest request, String dropParamterName) {
        this.baseURI = request.getRequestURL().toString();
        parseQueryString(request.getQueryString(), dropParamterName);
    }
    
    /**
     * 为地址增加查询参数值
     *
     * @param parameterName 查询参数名称
     * @param parameterValue 查询参数值
     * @return
     */
    public FrontPageURL addParameter(String parameterName, String parameterValue) {
        this.params.put(parameterName, addIfNoContains(parameterName, parameterValue));
        return this;
    }
    
    /**
     * 为地址增加查询参数值
     *
     * @param parameterName 查询参数名称
     * @param parameterValues 查询参数值数组
     * @return
     */
    public FrontPageURL addParameter(String parameterName, String[] parameterValues) {
        this.params.put(parameterName, addAllIfNoContains(parameterName, parameterValues));
        return this;
    }
    
    /**
     * 为地址增加查询参数值
     *
     * @param parameterName 查询参数名称
     * @param parameterValue 查询参数值
     * @return
     */
    public FrontPageURL addParameter(String parameterName, int parameterValue) {
        this.params.put(parameterName, addIfNoContains(parameterName, parameterValue + ""));
        return this;
    }
    
    /**
     * 为地址增加查询参数值
     *
     * @param parameterName 查询参数名称
     * @param parameterValues 查询参数值数组
     * @return
     */
    public FrontPageURL addParameter(String parameterName, int[] parameterValues) {
        this.params.put(parameterName, addAllIfNoContains(parameterName, Arrays.stream(parameterValues).mapToObj(String::valueOf).toArray(String[]::new)));
        return this;
    }
    
    /**
     * 为地址增加查询参数值
     *
     * @param parameterName 查询参数名称
     * @param parameterValue 查询参数值
     * @return
     */
    public FrontPageURL addParameter(String parameterName, long parameterValue) {
        this.params.put(parameterName, addIfNoContains(parameterName, parameterValue + ""));
        return this;
    }
    
    /**
     * 为地址增加查询参数值
     *
     * @param parameterName 查询参数名称
     * @param parameterValues 查询参数值数组
     * @return
     */
    public FrontPageURL addParameter(String parameterName, long[] parameterValues) {
        this.params.put(parameterName, addAllIfNoContains(parameterName, Arrays.stream(parameterValues).mapToObj(String::valueOf).toArray(String[]::new)));
        return this;
    }
    
    /**
     * 为地址增加分页时每页显示的记录数
     *
     * @param parameterName 查询参数名称
     * @param pageSize 每页显示的记录数
     * @return
     */
    public FrontPageURL addPageSize(String parameterName, int pageSize) {
        this.params.put(parameterName, addIfNoContains(parameterName, String.valueOf(checkPageSizeRange(pageSize))));
        this.pageSizeKeyName = parameterName;
        return this;
    }
    
    /**
     * 获取当前实例中的每页显示的记录数
     *
     * @return 如果先前没有调用addPageSize方法返回默认的值:50
     */
    public int getPageSize() {
        if (null == this.pageSizeKeyName) {
            return DEF_PAGE_SIZE;
        }
        int ps = DEF_PAGE_SIZE;
        List<String> vs = getKeyOfValue(this.pageSizeKeyName);
        if (vs.size() == 1) {
            ps = Commons.stringToInteger(vs.get(0), DEF_PAGE_SIZE);
        }
        return ps;
    }
    
    /**
     * 输出当前地址实例的结构,json格式
     *
     * @return
     */
    public String toJson() {
        if (this.params.isEmpty()) {
            return "{}";
        }
        return new Gson().toJson(params);
    }
    
    private List<String> getKeyOfValue(String key) {
        List<String> data = null;
        try {
            data = this.params.get(key);
        } catch (ClassCastException | NullPointerException e) {
        }
        if (null == data) {
            data = new ArrayList<>();
        }
        return data;
    }
    
    private List<String> addIfNoContains(String key, String value) {
        List<String> vs = getKeyOfValue(key);
        if (vs.isEmpty() || !vs.contains(value)) {
            vs.add(value);
        }
        return vs;
    }
    
    private List<String> addAllIfNoContains(String key, String[] values) {
        Set<String> unionRs = new HashSet<>(getKeyOfValue(key));
        unionRs.addAll(Arrays.asList(values));
        return new ArrayList<>(unionRs);
    }
    
    /**
     * 输出当前地址实例的结构,是一个路径 + ? + 查询字符串 html表单多选值,返回查询字符串格式为:key=value1,value2
     * html表单单选值,返回查询字符串格式为:key=value
     *
     * @return 如果无查询字符串返回路径
     */
    @Override
    public String toString() {
        if (this.params.isEmpty()) {
            return this.baseURI;
        }
        
        StringBuilder sb = new StringBuilder();
        sb.append(this.baseURI);
        sb.append("?");
        for (Entry<String, List<String>> entry : this.params.entrySet()) {
            sb.append(entry.getKey());
            sb.append("=");
            //20200616
            List<String> vs = entry.getValue();
            if (entry.getKey().equalsIgnoreCase(pageSizeKeyName)) {
                try {
                    sb.append(checkPageSizeRange(Commons.stringToInteger(vs.get(0), DEF_PAGE_SIZE)));
                } catch (IndexOutOfBoundsException e) {
                    sb.append(DEF_PAGE_SIZE);
                }
            } else {
                sb.append(String.join(",", vs));
            }
            sb.append("&");
        }
        //截掉最后一个字符
        try {
            return sb.toString().substring(0, sb.length() - 1);
        } catch (StringIndexOutOfBoundsException e) {
            return this.baseURI;
        }
    }
    /**
     * 输出前端分页需要的数据
     * @param pr DAORepository分页查询参数类型
     * @param records 总记录数
     * @return 
     */
    public FrontPageData toPageData(Pageable pr, final long records){
        final int ps = pr.getPageSize(); 
        final int p = pr.getPageNumber();
        final String pageURL = this.toString();
        
        return new FrontPageData(){
            @Override
            public String getPageURL() {
                return pageURL;
            }
            @Override
            public int getPageSize() {
                return ps;
            }
            @Override
            public long getRecords() {
                return records;
            }
            @Override
            public int getPage() {
                return p;
            }
        };
    }
    //将 {a=b&c=d&e=f}放到map中
    //multi select {a=b&a=c&a=d&c=d&e=f&e=g}
    //matrix variables {/owners/42;q=11/pets/21;q=22} or {/owners/42;q=11;r=12/pets/21;q=22;s=23}
    private void parseQueryString(String queryParams, String dropParamterName) {
        if (null == queryParams) {
            return;
        }
        String[] paramArray = queryParams.split("&");
        for (String param : paramArray) {
            String[] _kv = param.split("=");
            try {
                if (_kv.length != 2) {
                    continue;
                }
                if (null != dropParamterName && _kv[0].equals(dropParamterName)) {
                    continue;
                }
                if (!Commons.isNotBlank(_kv[1])) { //空值
                    continue;
                }
                this.params.put(_kv[0], addIfNoContains(_kv[0], _kv[1]));
            } catch (NullPointerException | IndexOutOfBoundsException e) {
            }
        }
    }
    
    private int checkPageSizeRange(int pageSize) {
        return (pageSize < MIN_PAGE_SIZE || pageSize > MAX_PAGE_SIZE) ? DEF_PAGE_SIZE : pageSize;
    }
}